/*
 * Security.cpp
 *
 *  Created on: Jan 14, 2015
 *      Author: wasp
 */

#include <stdio.h>
#include <iostream>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <QDir>
#include <qfile.h>
#include <qdebug.h>

#include <sbreturn.h>
#include <huctx.h>
#include <hurandom.h>
#include <hursa.h>
#include <hugse56.h>
#include <hugse56ver.h>
#include "../Utilities.hpp"
#include "../SBError.hpp"

#include "Security.hpp"

namespace de
{
    namespace heimweghelden
    {
        namespace tracking
        {

            Security* Security::_instance;
            Security* Security::getInstance() {
                if (_instance == 0) {
                    _instance = new Security;
                    _instance->init();
                }
                return _instance;
            }

            int Security::encryptSize() {
                return RSA_size(keystore);
            }

            bool Security::encrypt(QString input, unsigned char *output) {
                unsigned char* in = reinterpret_cast<unsigned char*>(input.toLocal8Bit().data());
                /*
                int code = hu_RSAPrivateEncrypt(rsaParams, privateKey, in, output, sbCtx);
                return code != SB_SUCCESS;*/
                int bufferSize = RSA_public_encrypt(input.length(), in, output, keystore, RSA_PKCS1_PADDING);
                return bufferSize != -1;
            }

            bool Security::decrypt(QString input, unsigned char *output) {
                //int code = hu_RSAPublicDecrypt(rsaParams, publicKey, input, output, sbCtx);
                unsigned char* in = reinterpret_cast<unsigned char*>(input.toLocal8Bit().data());
                int buffersize = RSA_public_decrypt(input.length(), in, output, keystore, RSA_PKCS1_PADDING);
                return buffersize != -1;
            }

            void Security::freeall() {
                if (_instance != 0) {
                    delete _instance;
                    _instance = 0;
                }
            }

            Security::Security()
            {
                sbCtx = NULL;
                qDebug() << "security init";
                bool success = init();
                qDebug() << "security done: " << (success ? "ja" : "nein");
                if (success) {
                    show();
                }
            }

            bool Security::init()
            {
                int code;
                sb_RNGCtx rngCtx;
                keystore = NULL;
                code = hu_GlobalCtxCreateDefault(&sbCtx);

                if (code != SB_SUCCESS) {
                    qDebug() << "XXXX makeHash ERROR creating SB contexts:"
                            << SBError::getErrorText(code);
                    return false;
                }

                // Register global context with GSE-C 5.6 Provider
                // GSE = Government Security Edition (SB GSE-C)
                // SB GSE-C is also known as BlackBerry OS Cryptographic Kernel

                code = hu_RegisterSbg56(sbCtx);
                if (code != SB_SUCCESS) {
                    qDebug() << "XXXX makeHash ERROR calling hu_RegisterSbg56:"
                            << SBError::getErrorText(code);
                    return false;
                }

                code = hu_InitSbg56(sbCtx);
                if ( !(code == SB_SUCCESS || code == SB_FAIL_LIBRARY_ALREADY_INIT)) {
                    qDebug() << "XXXX makeHash ERROR calling hu_InitSbg56:"
                            << SBError::getErrorText(code);
                    return false;
                }

                if (SB_SUCCESS
                        != (code = hu_RngCreate(0, NULL, NULL, NULL, NULL, &rngCtx, sbCtx))) {
                    qDebug() << "failed to created RNG ";
                    return false;
                }

                if (SB_SUCCESS
                        != (code = hu_RSAParamsCreate(4096, rngCtx, NULL, &rsaParams, sbCtx))) { //4096 - 512 damit der start schneller ist
                    qDebug() << "failed to created RSAParams " << code;
                    return false;
                }

                QDir datadir("data");
                if (!datadir.exists("certs")) {
                    if (!datadir.mkdir("certs")) {
                        qDebug() << "failed to create certs dir";
                        return false;
                    }
                } else {
                    QFile keyFilePriv("data/certs/rsa.key");
                    QFile keyFilePub("data/certs/rsa.pub");
                    if (keyFilePriv.exists() && keyFilePub.exists()) {
                        qDebug() << "RSA key pair exists";
                        //TODO: load private and public key
                        return loadKeys(&keyFilePriv, &keyFilePub);
                    }
                }

                if (SB_SUCCESS
                        != (code = hu_RSAKeyGen(rsaParams, 0, NULL, &privateKey, &publicKey, sbCtx))) {
                    qDebug() << "Could not generate RSA key" << SBError::getErrorText(code);
                    return false;
                }
                code = rsaGenerateAndExport(rsaParams);

                return code == SB_SUCCESS;
            }

            Security::~Security()
            {
                if (keystore != NULL) {
                    delete keystore;
                }
                int rc = hu_RSAParamsDestroy(&rsaParams, sbCtx);
                if (rc != SB_SUCCESS) {
                    qDebug() << "Warning: Could not destroy rsa params"
                            << SBError::getErrorText(rc);
                }
                _instance = 0;
                rc = hu_GlobalCtxDestroy(&sbCtx);
                qDebug() << "hu_GlobalCtxDestroy with code" << SBError::getErrorText(rc);
            }

            bool Security::loadKeys(QFile* keyFilePriv, QFile* keyFilePub) {
                keyFilePriv->open(QIODevice::ReadOnly);
                FILE* fh = fdopen(keyFilePriv->handle(), "rb");
                keystore = PEM_read_RSAPrivateKey(fh, NULL, NULL, NULL);
                if (keystore == NULL) {
                    qDebug() << "error reading private key";
                    ERR_print_errors_fp(stderr);
                    return false;
                }

                keyFilePub->open(QIODevice::ReadOnly);
                fh = fdopen(keyFilePub->handle(), "rb");
                RSA* tmp = NULL;
                tmp = PEM_read_RSAPublicKey(fh, &keystore, NULL, NULL);
                if (tmp == NULL) {
                    qDebug() << "error reading public key";
                    ERR_print_errors_fp(stderr);
                    return false;
                }

                unsigned char *e = new unsigned char[BN_num_bytes(keystore->e)];
                int eLen = BN_bn2bin(keystore->e, e);

                unsigned char *n = new unsigned char[BN_num_bytes(keystore->n)];
                int nLen = BN_bn2bin(keystore->n, n);

                unsigned char *d = new unsigned char[BN_num_bytes(keystore->d)];
                int dLen = BN_bn2bin(keystore->d, d);

                unsigned char *p = new unsigned char[BN_num_bytes(keystore->p)];
                int pLen = BN_bn2bin(keystore->p, p);

                unsigned char *q = new unsigned char[BN_num_bytes(keystore->q)];
                int qLen = BN_bn2bin(keystore->q, q);

                unsigned char *dModPm1 = new unsigned char[BN_num_bytes(keystore->dmp1)];
                int dModPLen = BN_bn2bin(keystore->dmp1, dModPm1);

                unsigned char *dModQm1 = new unsigned char[BN_num_bytes(keystore->dmq1)];
                int dModQLen = BN_bn2bin(keystore->dmq1, dModQm1);

                unsigned char *qInvModP = new unsigned char[BN_num_bytes(keystore->iqmp)];
                int qInvLen = BN_bn2bin(keystore->iqmp, qInvModP);

                int code = hu_RSAKeySet(rsaParams, eLen, e, nLen, n, dLen, d, pLen, p, qLen, q, dModPLen, dModPm1, dModQLen, dModQm1, qInvLen, qInvModP, &privateKey, &publicKey, sbCtx);

                delete e;
                delete n;
                delete d;
                delete p;
                delete q;
                delete dModPm1;
                delete dModQm1;
                delete qInvModP;

                return code == SB_SUCCESS;
            }

            int Security::rsaGenerateAndExport(sb_Params &rsaParams)
            {
                int rc;
                size_t eLen, nLen, dLen, pLen, qLen, dModPLen, dModQLen, qInvLen;

                size_t * sizes[] = { &eLen, &nLen, &dLen, &pLen, &qLen, &dModPLen, &dModQLen,
                        &qInvLen, NULL };
                zero(sizes);

                rc = hu_RSAKeyGet(rsaParams, privateKey, publicKey, &eLen, NULL, &nLen,
                NULL, &dLen, NULL, &pLen, NULL, &qLen, NULL, &dModPLen, NULL, &dModQLen, NULL,
                        &qInvLen, NULL, sbCtx);

                if (rc != SB_SUCCESS) {
                    qDebug() << "Could not do initial key get" << SBError::getErrorText(rc);
                } else {
                    unsigned char * e, *n, *d, *p, *q, *dModPm1, *dModQm1, *qInvModP;
                    unsigned char ** chars[] = { &e, &n, &d, &p, &q, &dModPm1, &dModQm1, &qInvModP,
                    NULL };
                    allocate(sizes, chars);

                    rc = hu_RSAKeyGet(rsaParams, privateKey, publicKey, &eLen, e, &nLen, n, &dLen,
                            d, &pLen, p, &qLen, q, &dModPLen, dModPm1, &dModQLen, dModQm1, &qInvLen,
                            qInvModP, sbCtx);
                    if (rc != SB_SUCCESS) {
                        qDebug() << "Could not get values" << SBError::getErrorText(rc);
                    } else {
                        BIGNUM * _e, *_n, *_d, *_p, *_q, *_dModPm1, *_dModQm1, *_qInvModP;
                        BIGNUM ** bigNums[] = { &_e, &_n, &_d, &_p, &_q, &_dModPm1, &_dModQm1,
                                &_qInvModP, NULL };
                        toBN(sizes, chars, bigNums);

                        keystore = new RSA();
                        keystore->n = _n;
                        keystore->e = _e;
                        keystore->d = _d;
                        keystore->p = _p;
                        keystore->q = _q;
                        keystore->dmp1 = _dModPm1;
                        keystore->dmq1 = _dModQm1;
                        keystore->iqmp = _qInvModP;

                        // Encode private key
                        {
                            BIO * privateBio = BIO_new(BIO_s_mem());
                            PEM_write_bio_RSAPrivateKey(privateBio, keystore, NULL, NULL, 0, NULL, NULL);
                            BUF_MEM * privateMem(NULL);
                            BIO_get_mem_ptr(privateBio, &privateMem);

                            save(privateMem, "rsa.key");

                            BIO_free(privateBio);
                        }

                        // Encode the public key
                        {
                            BIO * publicBio = BIO_new(BIO_s_mem());
                            PEM_write_bio_RSAPublicKey(publicBio, keystore);

                            BUF_MEM *publicMem(NULL);
                            BIO_get_mem_ptr(publicBio, &publicMem);

                            save(publicMem, "rsa.pub");

                            BIO_free(publicBio);
                        }

                        // The RSA object was not created via the library - so it isn't freed.
                        // In normal OpenSSL use - correct freeing of the RSA object would also
                        // free the BIGNUMs.
                        destroy(bigNums);
                    }

                    destroy(chars);
                }
                return rc;
            }

            void Security::save(BUF_MEM* buf, QString name)
            {
                QString cp = QDir::currentPath() + "/data/certs/" + name;
                QFile file(cp);
                file.open(QIODevice::WriteOnly | QIODevice::Text);
                QTextStream out(&file);
                out << buf->data;
                file.close();
            }

            void Security::dump(BUF_MEM* buf)
            {
                std::cout.write(buf->data, buf->length);
                std::cout << std::endl;
            }

            void Security::show() {
                // Encode private key
                {
                    BIO * privateBio = BIO_new(BIO_s_mem());
                    PEM_write_bio_RSAPrivateKey(privateBio, keystore, NULL, NULL, 0, NULL, NULL);
                    BUF_MEM * privateMem(NULL);
                    BIO_get_mem_ptr(privateBio, &privateMem);

                    dump(privateMem);

                    BIO_free(privateBio);
                }

                // Encode the public key
                {
                    BIO * publicBio = BIO_new(BIO_s_mem());
                    PEM_write_bio_RSAPublicKey(publicBio, keystore);

                    BUF_MEM *publicMem(NULL);
                    BIO_get_mem_ptr(publicBio, &publicMem);

                    dump(publicMem);

                    BIO_free(publicBio);
                }
            }



            void Security::zero(size_t** s)
            {
                while (*s != NULL) {
                    **s = 0;
                    ++s;
                }
            }

            void Security::allocate(size_t** s, unsigned char *** c)
            {
                while (*s != NULL && *c != NULL) {
                    **c = new unsigned char[**s];
                    ++s;
                    ++c;
                }
            }

            void Security::toBN(size_t ** s, unsigned char *** c, BIGNUM *** b)
            {
                while (*s != NULL && *c != NULL && *b != NULL) {
                    **b = BN_bin2bn(**c, **s, NULL);

                    ++s;
                    ++c;
                    ++b;
                }
            }

            void Security::destroy(unsigned char *** c)
            {
                while (*c != NULL) {
                    if (**c != NULL) {
                        delete[] **c;
                    }
                    ++c;
                }
            }

            void Security::destroy(BIGNUM *** b)
            {
                while (*b != NULL) {
                    BN_free(**b);
                    **b = NULL;
                    ++b;
                }
            }
        } /* namespace tracking */
    } /* namespace heimweghelden */
} /* namespace de */
